/*
 * Copyright (c) 2010, Cybera and/or its affiliates. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Cybera or the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * This software is provided by the copyright holders and contributors "as
 * is" and any express or implied warranties, including, but not limited to,
 * the implied warranties of merchantability and fitness for a particular
 * purpose are disclaimed.  In no event shall the copyright owner or
 * contributors be liable for any direct, indirect, incidental, special,
 * exemplary, or consequential damages (including, but not limited to,
 * procurement of substitute goods or services; loss of use, data, or
 * profits; or business interruption) however caused and on any theory of
 * liability, whether in contract, strict liability, or tort (including
 * negligence or otherwise) arising in any way out of the use of this
 * software, even if advised of the possibility of such damage.
 */ 
package org.cybera;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import org.cybera.ssh.SSH;

import com.xerox.amazonws.ec2.AttachmentInfo;
import com.xerox.amazonws.ec2.ImageDescription;
import com.xerox.amazonws.ec2.Jec2;
import com.xerox.amazonws.ec2.LaunchConfiguration;
import com.xerox.amazonws.ec2.ReservationDescription;
import com.xerox.amazonws.ec2.VolumeInfo;
import com.xerox.amazonws.ec2.ReservationDescription.Instance;

public class TypicaTutorial {
	public static final String ATTACHED = "attached";
	public static final String AVAILABLE = "available";

	private static final String MOUNT_POINT = "/volume1";
	private static final String EUCALYPTUS_URL = "ecc.eucalyptus.com";
	
	private Properties props = new Properties();
	private List<String> owners = new ArrayList<String>();
	private String sudo = "";
	private Jec2 cloud = null;
	
	public static void main(String[] args) throws Exception {
		TypicaTutorial typicaTutorial = new TypicaTutorial(args[0]);
		typicaTutorial.start(args[0]);
	}
	
	public TypicaTutorial(String arg) throws Exception {
		props.load(TypicaTutorial.class.getClassLoader().getResourceAsStream(arg.trim() + ".properties"));
		owners.add(props.getProperty("cloud.username").trim());
		sudo = props.getProperty("vmAuth.sudo").trim();
		
		if ("euca".equals(arg)) {
			cloud = new Jec2(props.getProperty("cloud.accessId").trim(), props.getProperty("cloud.secretKey").trim(), false, props.getProperty("cloud.URL").trim(), 8773);
			cloud.setResourcePrefix("/services/Eucalyptus"); 
			cloud.setSignatureVersion(1);
		}
		else {
			cloud = new Jec2(props.getProperty("cloud.accessId").trim(), props.getProperty("cloud.secretKey").trim());
		}
		
		System.out.println(arg + " " + props);		
	}
	
	private void start(String arg) throws Exception {		
		describeImages();
		Instance instance = runInstance(arg);
		AttachmentInfo volumeAttachmentInfo = createAttachAndMountVolume(instance);
		
		doInterestingStuff(instance);
		
		unmountDetachAndDeleteVolume(instance, volumeAttachmentInfo);
		terminateInstance(instance);
	}

	private void doInterestingStuff(Instance instance) throws Exception {		
		try {
			SSH.runCommandOnInstance("ping -c 1 google.com", instance, props);
		} catch (Exception e) {
			// print the error but continue on so the instance gets cleaned up
			e.printStackTrace();
		}		
	}

	private void describeImages() throws Exception {
		List<ImageDescription> images = cloud.describeImagesByOwner(owners);
		
		for (ImageDescription image : images) {
			System.out.println("Image ID = " + image.getImageId() + ", Owner = " + image.getImageOwnerId());
		}		
	}
	
	private Instance runInstance(String arg) throws Exception {
		LaunchConfiguration launchConfig = new LaunchConfiguration(props.getProperty("launchConfig.imageID").trim());
		launchConfig.setAvailabilityZone(props.getProperty("launchConfig.availabilityZone").trim());
		launchConfig.setKeyName(props.getProperty("launchConfig.keyName").trim());
		launchConfig.setMinCount(Integer.valueOf(props.getProperty("launchConfig.minCount").trim()));
		launchConfig.setMaxCount(Integer.valueOf(props.getProperty("launchConfig.maxCount").trim()));

		ReservationDescription reservationDescription = cloud.runInstances(launchConfig);
		Instance instance = reservationDescription.getInstances().get(0);
		String[] instances = new String[] {instance.getInstanceId()};

		// wait for the instance to start running 
		do {
			instance = cloud.describeInstances(instances).get(0).getInstances().get(0);
			
			System.out.println("Run: Instance ID = " + instance.getInstanceId() + ", State = " + instance.getState());
			
			Thread.sleep(5000);
		} while (!instance.isRunning());
		
		System.out.println("Run: Instance ID = " + instance.getInstanceId() + ", Public DNS Name = " + instance.getDnsName() + ", Private DNS Name = " + instance.getPrivateDnsName());		
		
		// takes some time for SSH to start on the VMs so sleep for a bit or we get a connection refused
		System.out.println("SSH: waiting for SSH on VM to start");
		Thread.sleep(30000); 
		
		return instance;
	}

	private AttachmentInfo createAttachAndMountVolume(Instance instance) throws Exception {
		if (EUCALYPTUS_URL.equals(props.getProperty("cloud.URL"))) {
			System.out.println("Create: Cannot create storage volumes on ECC :(");
			return null;
		}
		
		VolumeInfo volumeInfo = cloud.createVolume("2", null, props.getProperty("launchConfig.availabilityZone").trim());
		String[] volumes = new String[] {volumeInfo.getVolumeId()};

		// wait for the volume to be created
		do {
			volumeInfo = cloud.describeVolumes(volumes).get(0);
			
			System.out.println("Create: Volume ID = " + volumeInfo.getVolumeId() + ", State = " + volumeInfo.getStatus());
			
			Thread.sleep(2000);
		} while (!AVAILABLE.equals(volumeInfo.getStatus().toLowerCase()));

		AttachmentInfo volumeAttachmentInfo = cloud.attachVolume(volumeInfo.getVolumeId(), instance.getInstanceId(), props.getProperty("attachVolume.deviceName").trim());
		
		// wait for the volume to be attached
		do {
			List<AttachmentInfo> volumeAttachmentInfos = cloud.describeVolumes(volumes).get(0).getAttachmentInfo();
			
			volumeAttachmentInfo = volumeAttachmentInfos.get(0);
			
			System.out.println("Attach: Volume ID = " + volumeAttachmentInfo.getVolumeId() + ", State = " + volumeAttachmentInfo.getStatus());
			
			Thread.sleep(2000);
		} while (!ATTACHED.equals(volumeAttachmentInfo.getStatus().toLowerCase()));
		
		ArrayList<String> commandStrs = new ArrayList<String>();
		commandStrs.add(sudo + " mkfs.ext3 -F -q " + props.getProperty("attachVolume.deviceName").trim());
		commandStrs.add(sudo + " mkdir " + MOUNT_POINT);
		commandStrs.add(sudo + " mount " + props.getProperty("attachVolume.deviceName").trim() + " " + MOUNT_POINT);
		
		try {
			SSH.runCommandsOnInstance(commandStrs, instance, props);
		} catch (Exception e) {
			// print the error but continue on so the instance gets cleaned up
			e.printStackTrace();
		}		
		
		return volumeAttachmentInfo;
	}

	private void unmountDetachAndDeleteVolume(Instance instance, AttachmentInfo volumeAttachmentInfo) throws Exception {
		if (EUCALYPTUS_URL.equals(props.getProperty("cloud.URL"))) {
			System.out.println("Delete: Cannot create storage volumes on ECC :(");
			return;
		}
		
		try {
			SSH.runCommandOnInstance(sudo + " umount " + MOUNT_POINT, instance, props);
		} catch (Exception e) {
			// print the error but continue on so the instance gets cleaned up
			e.printStackTrace();
		}		
		
		cloud.detachVolume(volumeAttachmentInfo.getVolumeId(), volumeAttachmentInfo.getInstanceId(), volumeAttachmentInfo.getDevice(), false);
		String[] volumes = new String[] {volumeAttachmentInfo.getVolumeId()};
		VolumeInfo volumeInfo;

		// wait for the volume to detach
		do {
			volumeInfo = cloud.describeVolumes(volumes).get(0);
			
			System.out.println("Detach: Volume ID = " + volumeInfo.getVolumeId() + ", State = " + volumeInfo.getStatus());
			
			Thread.sleep(2000);
		} while (!AVAILABLE.equals(volumeInfo.getStatus().toLowerCase()));
		
		// don't wait for the volume to delete as this can take some time and it's not necessary to wait to move forward
		cloud.deleteVolume(volumeAttachmentInfo.getVolumeId());

		System.out.println("Delete: Volume ID = " + volumeInfo.getVolumeId() + ", State = deleting");
	}

	private void terminateInstance(Instance instance) throws Exception {
		String[] instances = new String[] {instance.getInstanceId()};

		try {
			// don't wait for the instance to terminate as this can take some time and it's not necessary to wait to move forward
			cloud.terminateInstances(instances);
		} catch (NullPointerException e) {
			 // the call to terminate the instance goes through but it looks like Eucalyptus returns a null value and typica chokes on it, ignore it
			 // see http://code.google.com/p/typica/issues/detail?id=105
		}

		System.out.println("Terminate: Instance ID = " + instance.getInstanceId() + ", State = shutting-down");
	}
}
